using System.Net;
using System.Web;
using Gateway.Common.Cache;
using Gateway.Common.Models;
using Gateway.Domain.Entities;
using Gateway.IApplication.UploadFiles;
using Gateway.IApplication.UploadFiles.Input;
using Gateway.IApplication.UploadFiles.Output;
using Gateway.IRepositories;
using Aurora.Redis;
using Ionic.Zip;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Gateway.Application;

/// <summary>
/// 上传文件服务
/// </summary>
public class UploadFilesApp : IUploadFilesApp
{
    private readonly IAppRepository<UploadFiles> _repository;
    private readonly IUnitOfWork _unitOfWork;
    private readonly IOptionsMonitor<UploadOption> _option;
    private readonly ILogger<UploadFilesApp> _logger;
    private readonly IServiceScopeFactory _scopeFactory;

    /// <summary>
    /// 
    /// </summary>
    /// <param name="repository"></param>
    /// <param name="unitOfWork"></param>
    /// <param name="option"></param>
    /// <param name="logger"></param>
    /// <param name="scopeFactory"></param>
    public UploadFilesApp(IAppRepository<UploadFiles> repository, IUnitOfWork unitOfWork,
        IOptionsMonitor<UploadOption> option, ILogger<UploadFilesApp> logger, IServiceScopeFactory scopeFactory)
    {
        _repository = repository;
        _unitOfWork = unitOfWork;
        _option = option;
        _logger = logger;
        _scopeFactory = scopeFactory;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="stream"></param>
    /// <param name="fileName"></param>
    /// <param name="uploadId"></param>
    /// <param name="partSize"></param>
    /// <param name="partNumber"></param>
    /// <returns></returns>
    /// <exception cref="MyException"></exception>
    public async Task<MultipartUploadOutput> PartUploadAsync(Stream stream, string fileName, string uploadId,
        long partSize, int partNumber)
    {
        string tmpFileName = string.Empty;
        string tmpFilePath = string.Empty;
        if (string.IsNullOrEmpty(uploadId))
        {
            var extName = Path.GetExtension(fileName);
            if (string.IsNullOrEmpty(extName))
            {
                throw new MyException("文件名缺少扩展名");
            }

            extName = extName.Substring(1).ToLower();
            var file = new UploadFiles(length: partNumber * partSize, originName: fileName, extName: extName);
            tmpFileName = file.FileNo + "." + extName;
            uploadId = file.Id;
            await _repository.AddAsync(file);
            await _unitOfWork.CommitAsync();
        }
        else
        {
            var fileInfo = await _repository.GetAsync(uploadId);
            if (fileInfo != null)
            {
                tmpFileName = fileInfo.FileNo +
                              Path.GetExtension(fileInfo.Path).ToLower();
                tmpFilePath = fileInfo.Path;
            }
        }

        await CreatePartLocalFileAsync(uploadId, partNumber, stream, tmpFileName);

        return new MultipartUploadOutput()
        {
            UploadId = uploadId,
            Path = tmpFilePath
        };
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="path"></param>
    /// <param name="uploadId"></param>
    /// <returns></returns>
    public Task<string> FinishMultipartUploadAsync(string path, string uploadId)
    {
        _ = Task.Factory.StartNew(async () =>
        {
            using (var scope = _scopeFactory.CreateScope())
            {
                var repository = scope.ServiceProvider.GetService<IAppRepository<Domain.Entities.UploadFiles>>();
                var localFileInfo = await repository.GetAsync(uploadId);
                if (localFileInfo != null)
                {
                    try
                    {
                        await MergePartLocalFileAsync(uploadId, localFileInfo.Path);
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(e);
                    }

                    var fileRootPath = _option.CurrentValue.FilePath;
                    var newPath = localFileInfo.Path.Replace(fileRootPath, "");
                    if (!newPath.StartsWith("/"))
                    {
                        newPath = "/" + newPath;
                    }

                    string fullPath = _option.CurrentValue.Domain + newPath;
                    var redis = scope.ServiceProvider.GetService<IRedisCache>();
                    redis.Add(CacheKeys.UploadFileProgress + uploadId, new FinishUploadOutput()
                    {
                        Url = fullPath,
                        Name = localFileInfo.OriginName
                    }, TimeSpan.FromDays(2));
                }
            }
        }, TaskCreationOptions.LongRunning);


        return Task.FromResult<string>(uploadId);
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="keyId"></param>
    /// <returns></returns>
    public Task<FinishUploadOutput> GetUploadStatus(string keyId)
    {
        using (var scope = _scopeFactory.CreateScope())
        {
            var redis = scope.ServiceProvider.GetService<IRedisCache>();
            var data = redis.Get<FinishUploadOutput>(CacheKeys.UploadFileProgress + keyId);
            return Task.FromResult(data);
        }
    }


    /// <summary>
    /// 分片上传  创建本地文件
    /// </summary>
    /// <param name="uploadId">上传的id</param>
    /// <param name="partNumber">当前分片编号</param>
    /// <param name="file">文件留</param>
    /// <param name="fileName">文件名</param>
    /// <returns></returns>
    public async Task<bool> CreatePartLocalFileAsync(string uploadId, int partNumber, Stream file,
        string fileName)
    {
        //获取hash
        if (file != null)
        {
            var filePath = $"PartTemp/{uploadId}";

            var tmpPath = GetRawFilePath(filePath);
            var partTmpPath = tmpPath + $"/partNumber-{partNumber}--{fileName}";
            using (var stream = System.IO.File.OpenWrite(partTmpPath))
            {
                await file.CopyToAsync(stream);
            }
        }

        return true;
    }

    /// <summary>
    /// 分片上传  合并分片文件
    /// </summary>
    /// <param name="uploadId">上传的id</param>
    /// <param name="targetFilePath">目标文件</param>
    /// <returns></returns>
    public async Task<bool> MergePartLocalFileAsync(string uploadId, string targetFilePath)
    {
        var partTmpPath = $"PartTemp/{uploadId}";
        var tmpPath = GetRawFilePath(partTmpPath);
        //获取文件夹下的所有文件
        var fileList = Directory.GetFiles(tmpPath);
        if (!fileList.Any())
        {
            _logger.LogWarning($"当前文件夹下:{tmpPath}的文件为空");
            return false;
        }

        var partToken = "partNumber-";
        var mergeFiles = new List<MergeFileSort>();
        foreach (var file in fileList)
        {
            var sort = new MergeFileSort()
            {
                FileName = file
            };
            var tmpFileName = file.Split("--")[0];
            var fileIndex = tmpFileName.Substring(tmpFileName.IndexOf(partToken) + partToken.Length);
            var success = int.TryParse(fileIndex, out var number);
            if (!success)
            {
                continue;
            }

            sort.PartNumber = number;
            mergeFiles.Add(sort);
        }

        var mergeOrders = mergeFiles.OrderBy(s => s.PartNumber).ToList();
        // 合并文件
        var fileName = Path.GetFileName(targetFilePath);
        var fullTargetPath = GetRawFilePath(targetFilePath.Replace(fileName, "").TrimEnd('/'));
        var fullName = fullTargetPath + "/" + fileName;
        var extName = Path.GetExtension(fileName);
        using (var fileStream = new FileStream(fullName, FileMode.Create))
        {
            foreach (var fileSort in mergeOrders)
            {
                using (FileStream fileChunk =
                       new FileStream(fileSort.FileName, FileMode.Open))
                {
                    await fileChunk.CopyToAsync(fileStream);
                }

                System.IO.File.Delete(fileSort.FileName);
            }
        }

        if (extName.ToLower().Equals(".zip") || extName.ToLower().Equals(".rar"))
        {
            var tempName = fullTargetPath + "/" + Path.GetFileNameWithoutExtension(fileName);
            var backPath = tempName + "_backup" + extName;
            if (extName.ToLower().Equals(".rar"))
            {
                int bufferSize = 8192; // 缓冲区大小，可以根据需要进行调整
                // using (Stream sourceStream = File.OpenRead(fullName))
                // using (Stream destinationStream = File.Create(backPath))
                // {
                //     sourceStream.CopyTo(destinationStream);
                // }
                using (FileStream sourceStream = File.OpenRead(fullName))
                using (FileStream destinationStream = File.Create(backPath))
                {
                    byte[] buffer = new byte[bufferSize];
                    int bytesRead;

                    while ((bytesRead = sourceStream.Read(buffer, 0, bufferSize)) > 0)
                    {
                        destinationStream.Write(buffer, 0, bytesRead);
                    }
                }
            }
            else
            {
                const int bufferSize = 1024 * 1024 * 50; // 设置缓冲区大小  50M
                byte[] buffer = new byte[bufferSize];
                int bytesRead;
                // // 使用 ZipFileExtensions 类的修复方法修复损坏的 ZIP 文件
                using (ZipFile zipFile = new ZipFile(fullName))
                {
                    using (FileStream sourceStream = File.OpenRead(fullName))
                    {
                        using (FileStream destinationStream = File.Create(backPath))
                        {
                            while ((bytesRead = sourceStream.Read(buffer, 0, buffer.Length)) > 0)
                            {
                                destinationStream.Write(buffer, 0, bytesRead);
                            }
                        }
                    }

                    zipFile.Save(backPath);
                }
            }

            //如果处理成功 那么把原文件删除 然后再把backup文件重命名
            System.IO.File.Delete(fullName);
            File.Move(backPath, fullName);
        }


        //删除文件
        foreach (var file in mergeOrders)
        {
            if (File.Exists(file.FileName))
                System.IO.File.Delete(file.FileName);
        }

        var t = Path.Combine(tmpPath, "");
        Directory.Delete(t);
        return true;
    }

    /// <summary>
    /// 获取文件真实的路径 根据文件地址
    /// </summary>
    private string GetRawFilePath(string path)
    {
        var dirPath = Path.Combine(
            _option.CurrentValue.RootPath,
            path
        );
        if (!Directory.Exists(dirPath))
            Directory.CreateDirectory(dirPath);
        return dirPath;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="stream"></param>
    /// <param name="fileName"></param>
    /// <param name="size"></param>
    /// <param name="provider"></param>
    /// <param name="contentType"></param>
    /// <returns></returns>
    public async Task<FileOutput> UploadFile(Stream stream, string fileName, long size, string provider,
        string contentType)
    {
        if (_option.CurrentValue.ContentType?.Count > 0 && !_option.CurrentValue.ContentType.Contains(contentType))
        {
            throw MyException.Create(400, "文件类型不支持");
        }

        size = size / 1024;
        if (size > _option.CurrentValue.MaxSize)
        {
            throw MyException.Create(400, "文件大小超出限制");
        }

        string path = _option.CurrentValue.RootPath;


        string dirPath = Path.Combine(path);
        if (!Directory.Exists(dirPath))
            Directory.CreateDirectory(dirPath);
        var file = new UploadFiles(length: size, originName: fileName,
            extName: Path.GetExtension(fileName).Substring(1).ToLower());

        string filePath = Path.Combine(dirPath,
            file.Path);

        var newFileName = Path.GetFileName(file.Path);
        var fullTargetPath = GetRawFilePath(file.Path.Replace(newFileName, "").TrimEnd('/'));
        var fullName = Path.Combine(fullTargetPath, newFileName);

        using (var stream1 = System.IO.File.OpenWrite(fullName))
        {
            await stream.CopyToAsync(stream1);
        }

        await _repository.AddAsync(file);
        await _unitOfWork.CommitAsync();

        var newPath = filePath.Replace(path, "");
        if (!newPath.StartsWith("/"))
        {
            newPath = "/" + newPath;
        }

        string fullPath = _option.CurrentValue.Domain + newPath;
        return new FileOutput()
        {
            Id = file.Id,
            FileName = file.OriginName,
            Url = fullPath,
            Size = size.ToString(),
            FilePath = file.Path,
            Provider = file.StorageKey
        };
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="fileUrl">在线文件流</param>
    /// <param name="size"></param>
    /// <returns></returns>
    public async Task<FileOutput> UploadFile(string fileUrl)
    {
        string path = _option.CurrentValue.RootPath;
        var stream = GetStreamByFile(fileUrl);
        Uri uri = new Uri(fileUrl);
        var size = stream.Length / 1024;
        string fileName = HttpUtility.UrlDecode(uri.Segments.Last()).Replace(@"/","");
        string dirPath = Path.Combine(path);
        if (!Directory.Exists(dirPath))
            Directory.CreateDirectory(dirPath);
        var file = new UploadFiles(length:size, originName: fileName,
            extName: Path.GetExtension(fileName).Substring(1).ToLower());

        string filePath = Path.Combine(dirPath,
            file.Path);

        var newFileName = Path.GetFileName(file.Path);
        var fullTargetPath = GetRawFilePath(file.Path.Replace(newFileName, "").TrimEnd('/'));
        var fullName = Path.Combine(fullTargetPath, newFileName);

        using (var fs = new FileStream(fullName,FileMode.OpenOrCreate,FileAccess.Write))
        {
           await fs.WriteAsync(stream, 0, stream.Length);
        }
        await _repository.AddAsync(file);
        await _unitOfWork.CommitAsync();

        var newPath = filePath.Replace(path, "");
        if (!newPath.StartsWith("/"))
        {
            newPath = "/" + newPath;
        }

        string fullPath = _option.CurrentValue.Domain + newPath;
        return new FileOutput()
        {
            Id = file.Id,
            FileName = file.OriginName,
            Url = fullPath,
            Size = stream.Length.ToString(),
            FilePath = file.Path,
            Provider = file.StorageKey
        };
    }

    /// <summary>
    /// 获取文件流
    /// </summary>
    /// <param name="fileUrl"></param>
    /// <returns></returns>
    private byte[] GetStreamByFile(string fileUrl)
    {
        try
        {
            if (fileUrl.ToLower().StartsWith("https"))
            {
                System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
                ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
            }

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(fileUrl);
            if (fileUrl.ToLower().StartsWith("https"))
            {
                request.ServerCertificateValidationCallback = delegate { return true; };
                request.ProtocolVersion = HttpVersion.Version10;
            }

            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
            var responseStream = response.GetResponseStream();
            List<byte> btlst = new List<byte>();
            int b = responseStream.ReadByte();
            while (b > -1)
            {
                btlst.Add((byte)b);
                b = responseStream.ReadByte();
            }

            byte[] bts = btlst.ToArray();
            response.Dispose();
            response.Close();
            responseStream.Close();
            responseStream.Dispose();
            return bts;
        }
        catch (Exception ex)
        {
            _logger.LogError("下载图片异常，图片地址：" + fileUrl + "，异常信息为：" + ex.Message);
        }

        return null;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    /// <exception cref="MyException"></exception>
    public async Task<bool> Remove(string id)
    {
        var file = await _repository.GetAsync(id);
        if (file == null)
            throw new MyException(SystemErrorCode.ModelValidateError);
        file.SetDeleted();
        await _repository.UpdateAsync(file);
        await _unitOfWork.CommitAsync();
        return true;
    }
}